﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;


namespace InvariantWM
{    
    public class Watermarker
    {
        public string key;

        /*secret key K to randomly select n pair of mid-frequency DCT coefficients following the Zig-Zag scan order.*/
        public int k;

        /*watermark bits w for insertion*/
        public int[] w;
        
        private int N = 20;
        private double T = 0.7;
        private double coeff = 0.3;

        /*T is a threshold to control watermark invisibility*/
        Coeficientes[] listaCoeficientes;

        public Watermarker(string clave)
        {
            this.key = clave;
            long keyNumber = SetHashCode(clave);
            this.k = crearClaveBinaria(keyNumber);
            listaCoeficientes = ZigZag(44);
        }
        /*
         * double[,] DCT.directTransform(double[,] srcMatrix, uint xSize, uint ySize); //Esto pa la directa.       
         * We embed one bit of the watermark into each pair of the DCT coefficients. For the watermark bits w = {w1,w2,…, wn},*/
        public double[,] InsertWatermark(double[,] dct)
        {
            if (dct.Length < ((k * 2) + (dct.Length / 2)))
                throw new Exception("La marca de agua no cabe en la matriz, escoger tamaño de parche mayor");

            int xSize = dct.GetLength(0);
            int ySize = dct.GetLength(1);
  
            double[,] markDCT = new double[xSize, ySize];
            
            //copiamos la matriz            
            for (int i = 0; i < xSize; i++)
                for (int j = 0; j < ySize; j++)
                {
                    markDCT[i, j] = dct[i, j];
                }

            int marcados = 0;     

            for (int i = 0; i < k ; i++)
            {
                int indC1x = listaCoeficientes[i * 2].x;
                int indC1y = listaCoeficientes[i * 2].y;
                int indC2x = listaCoeficientes[i * 2 + 1].x;
                int indC2y = listaCoeficientes[i * 2 + 1].y;

                double c1, c2; 
                double C1;
                double C2;
                double D = 0;

                c1 = dct[indC1x, indC1y];
                c2 = dct[indC2x, indC2y];

                // Por si los valores no cambian
                C1 = c1;
                C2 = c2;

                D = (Math.Abs(c1) + Math.Abs(c2)) / (2 * Math.Max(Math.Abs(c1), Math.Abs(c2)));
                //D = Math.Abs((c1 + c2)  / (2 * Math.Max(c1, c2)));



                if (w[i] == 1)
                {
                
                   if (D > T)
                    {
                        /*
                        C1 = c1 + (T - D) / 2;
                        C2 = c2 - (T - D) / 2;
                         * 
                        C1 = c1 + (Math.Abs(c1) * coeff);
                        C2 = c2 - (Math.Abs(c2) * coeff);
                         * 
                        C1 = Math.Max(c1, c2);
                        C2 = Math.Min(c1, c2);
                         
                        
                        while (Math.Abs(C1) < Math.Abs(C2))
                        {
                            C1 += Math.Abs(c2) * coeff;
                            //C2 -= c2 * coeff;
                        }
                        
                        */
                        C1 = (1 + coeff) * c1;
                        C2 = (1 - coeff) * c2;

                        marcados ++;
                   }                  
                }
                else
                {                
                    if (D > T)
                    {
                        /*
                        C1 = c1 - (T - D) / 2;
                        C2 = c2 + (T - D) / 2;
                         * 
                        C1 = c1 - (Math.Abs(c1) * coeff);
                        C2 = c2 + (Math.Abs(c2) * coeff);
                         * 
                        C1 = Math.Min(c1, c2);
                        C2 = Math.Max(c1, c2);

 
                        while (Math.Abs(C1) > Math.Abs(C2))
                        {
                            //C1 -= c1 * coeff;
                            C2 += Math.Abs(c1) * coeff;
                        }
                        
*/
                        C1 = (1 - coeff) * c1;
                        C2 = (1 + coeff) * c2;

                        marcados ++;
                    }
                }               
                markDCT[indC1x, indC1y] = C1;
                markDCT[indC2x, indC2y] = C2;           
            }
            return markDCT;
        }
        /*
       double[,] DCT.inverseTransform(double[,] srcMatrix, uint xSize, uint ySize); //Esto pa la inversa.
         * Watermark extraction is achieved by comparing the coefficients c1 and c2 within each pair asfollows:
         * Wi = if (c1>=c2)
         *      1
         *      else
         *      0
         *w is the extracted watermark      
         *Finally, the normalized correlation (NC) is used to evaluate the similarities of the watermark.
         *nc = sum(Wi * wi)/ (sqrt(sum Wi^2) (sqrt(sum wi^2)
         *
         * Devuelvo la Marca de Agua Extraida de la DCT la W*/
        public int[] ExtractWatermark(double[,] Udct)
        {          
            double c1, c2;
            int[] extractedW = new int[k];
            //se supone que no hay mas coeficientes que espacios en la W
            for (int i = 0; i < k ; i++)
            {
                int indc1x = listaCoeficientes[i * 2].x;
                int indc1y = listaCoeficientes[i * 2].y;
                int indc2x = listaCoeficientes[i * 2 + 1].x;
                int indc2y = listaCoeficientes[i * 2 + 1].y;

                /*
                c1 = Math.Abs(Udct[indc1x, indc1y]);
                c2 = Math.Abs(Udct[indc2x, indc2y]);
                */
                c1 = Udct[indc1x, indc1y];
                c2 = Udct[indc2x, indc2y];

                if (c1 >= c2)
                    extractedW[i] = 1;
                else
                    extractedW[i] = 0;
            }
            return extractedW;
        }

        /* 
         * nc = sum(Wi * wi)/ (sqrt(sum Wi^2) (sqrt(sum wi^2)
         * Devuelve el valor de NC, siendo w la marca original, y W la marca extraida
         */
        public double calcularNC(int[] W)
        {
            /*sum(Wi * wi)*/
            double sum1 = 0;
            /*(sum Wi^2)*/
            double sum2 = 0;
            /*sum wi^2)*/
            double sum3 = 0;
            for(int i = 0 ; i < k ; i++)
            {
                sum1 += W[i] * w[i];
            }
            //son 0 y 1, el cuadrado y la raiz no le hacen nada
            foreach (int x in W)
            {
                sum2 += x;
            }
            foreach (int x in w)
            {
                sum3 += x;
            }
            return  (sum1 / (Math.Sqrt(sum2) * Math.Sqrt(sum3)));
        }


        /*Actualiza los valores de k y de w a partir de la clave.*/
        private int crearClaveBinaria(long clave){
            int tam = (int)Math.Log(clave, 2);
            
            w = new int[tam];
            long auxAcum = 0;
            long actual = 0;
            for (int i = 0; i < tam; i++)
            { 
                //La clave se almacena de mayor a menor valor
                auxAcum += (long)Math.Pow(2, tam - i);
                if (auxAcum <= clave)
                {
                    w[i] = 1;
                    actual = auxAcum;
                }
                else
                {
                    w[i] = 0;
                    auxAcum = actual;
                }
            }
            if (actual < clave)
            {
                w[0] = 1;
                actual++;
            }

            if (clave != actual)
                throw new Exception("Valores de clave diferentes, mirar crearClaveBinaria");
            return tam;
        }
        
        //Recorrido en zig zag de una matriz con el doble de posiciones que el tamaño de k para que se puedan coger 2 coeficientes en vez de uno
        private Coeficientes[] ZigZag(int size)
        {
            Coeficientes[] listaCoeficientes = new Coeficientes[k * 2];
            int i = N;
            int j = N;
            for (int tam = 0; tam < k * 2; tam++)
            {
                listaCoeficientes[tam] = new Coeficientes(j,i);
                if ((i + j) % 2 == 0)
                {
                    // Even stripes
                    if (j < size)
                    {
                        j++;
                    }
                    else
                    {
                        i += 2;
                    }
                    if (i > 1)
                    {
                        i--;
                    }
                }
                else
                {
                    // Odd stripes
                    if (i < size)
                    {
                        i++;
                    }
                    else
                    {
                        j += 2;
                    }
                    if (j > 1)
                    {
                        j--;
                    }
                }
            }
            return listaCoeficientes;
        }

        private long SetHashCode(String clave)
        {
            uint hash = 0xAAAAAAAA;

            for (int i = 0; i < clave.Length; i++)
            {
                hash ^= ((i & 1) == 0) ? ((hash << 7) ^ clave[i] * (hash >> 3)) :
                                        (~((hash << 11) + clave[i] ^ (hash >> 5)));
            }

            return hash;
        }

    }
    public class Coeficientes
    {
        public int x;
        public int y;

        public Coeficientes(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
}

/*Step 3 For the watermark bits w = {w1, w2,…, wn}, use a
        secret key K to randomly select n pair of mid-frequency DCT
        coefficients following the Zig-Zag scan order. It should be
        noted that for each pair, the two coefficients are adjacent to
        each other.
     * 
     Step 4 Assume that c1 and c2 are the DCT coefficients
        within each pair. We embed one bit of the watermark into each
        pair of the DCT coefficients. For the watermark bits w = {w1,
        w2,…, wn}, embed them as follows:
        If wi = 1 and D = c1 - c2 < T (here T is a threshold to
        control watermark invisibility), increase c1 whereas decrease
        c2 by inserting the watermark as follows: 
            c1' = c1 + (T-D)/2
            c2' = c2 - (T-D)/2
        Else if D = c1 - c2 > T, do nothing
        If wi = 0 and D = c2 - c1 < T, similar operation is done as
        follows:
            c1' = c1 - (T-D)/2
            c2' = c2 + (T-D)/2
         Else if D = c2 - c1 > T do nothing.
         */
/*
 Voy a explicar un poco como se usa.

Se crea un objeto de watermark
new Watermark(key)

key : es el entero que usaremos de clave.
w : es el array de numeros binarios (la key pasada a binario)
W : es el array donde meteremos la marca de agua extraida, inicialmente a 0
k: longitud de la clave
T: esto es para controlar que la marca de agua sea o no visible, inicialmente la he puesto a 2
NC : es el valor promedio en el que se parece la clave extraida a la clave original.

INSERCION
el paso 3 del algoritmo se soluciona de forma interna.
el paso 4.
para insertar la clave en un cuadradito normalizado

public double[,] InsertWatermark(double[,] DCT, uint xSize, uint ySize)

y ya tenemos el cuadradito marcado!!!
repitase el paso 4 para todos los colores si es preciso :D

EXTRACCION
a cada cuadradito que le queramos sacar la marca de agua
public int[] ExtractWatermark(double[,] DCT)
esto nos devolverá la W extraida
ahora hacemos
 public double calcularNC(int[] w, int[] W)
para obtener el NC entre las dos marcas de agua.

y listo amigos.*/
